package com.koylu.faces.component.validator;

import java.io.InputStream;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.ResourceBundle;

import javax.faces.application.Application;
import javax.faces.application.FacesMessage;
import javax.faces.component.StateHolder;
import javax.faces.component.UIComponent;
import javax.faces.component.UIInput;
import javax.faces.context.FacesContext;
import javax.faces.validator.Validator;
import javax.faces.validator.ValidatorException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.koylu.validator.Rule;
import com.koylu.validator.ValidatorManager;

public class JSFValidator implements Validator, StateHolder {
	
	private static String config = "validator";
	private static Log logger    = null;
	private static ValidatorManager validatorManager;	
	
	static{		
		logger = LogFactory.getLog(JSFValidator.class);
		validatorManager = new ValidatorManager();
		
		InputStream configStream = JSFValidator.class.getResourceAsStream("/com/koylu/validator/validator.xml");
		try {
			validatorManager.addValidator(configStream);	
		} catch (Exception e) {
			logger.error(e);
		}		
		String url = FacesContext.getCurrentInstance().getExternalContext().getInitParameter(config);
		if(url != null){
			try {
				configStream = FacesContext.getCurrentInstance().getExternalContext().getResourceAsStream(url);
				validatorManager.addValidator(configStream);	
			} catch (Exception e) {
				logger.error(e);
			}
			
		}
		logger.info("VALIDATOR MANAGER INITIALIZED-->"+validatorManager);
	}
	
	public final String DEFAULT_BUNDLE_NAME = "javax.faces.Messages";
	
	private String name;
	private String rel;
	private String mask;
	private String min;
	private String max;
	private String message;
	private boolean _transient = false;

	public JSFValidator() {
		
	}
	
	public void validate(FacesContext facesContext, UIComponent uiComponent, Object value) {		
		Map map = new HashMap();		
		Object submittedValue = ((UIInput)uiComponent).getSubmittedValue();
		map.put("submittedValue", submittedValue);
		if(rel != null){
			UIInput relComp = (UIInput)findComponent(rel);
			if(relComp.getSubmittedValue() != null){
				map.put("relValue", relComp.getSubmittedValue());	
			}else{
				map.put("relValue", relComp.getValue());
			}			
		}
		if(mask != null){
			map.put("mask", mask);
		}
		if(min != null){
			map.put("min", min);
		}
		if(max != null){
			map.put("max", max);
		}
		if(message != null){
			map.put("message", message);
		}		
		Rule rule = validatorManager.findRule(name);
		Boolean result = null;		
		try {
			 result = rule.validate(map);	
		} catch (Exception e) {
			result = new Boolean(false);
			logger.error(e);
		}		
		if(!result.booleanValue()){
			String rowMessage = getBundleMessage(facesContext, rule.getMsg());
			String errorMessage = rule.getErrorMessage(rowMessage, map);
			FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_ERROR,errorMessage,errorMessage);
			throw new ValidatorException(message);
		}
	}
	
	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getRel() {
		return rel;
	}

	public void setRel(String rel) {
		this.rel = rel;
	}

	public String getMask() {
		return mask;
	}

	public void setMask(String mask) {
		this.mask = mask;
	}

	public String getMin() {
		return min;
	}

	public void setMin(String min) {
		this.min = min;
	}

	public String getMax() {
		return max;
	}

	public void setMax(String max) {
		this.max = max;
	}

	public String getMessage() {
		return message;
	}

	public void setMessage(String message) {
		this.message = message;
	}

	public boolean isTransient() {
		return _transient;
	}

	public void setTransient(boolean transientValue) {
		_transient = transientValue;
	}

	public Object saveState(FacesContext context) {
		Object values[] = new Object[6];
		values[0] = name;
		values[1] = rel;
		values[2] = mask;
		values[3] = min;
		values[4] = max;
		values[5] = message;
		return values;
	}

	public void restoreState(FacesContext context, Object state) {
		Object values[] = (Object[]) state;
		name = (String) values[0];
		rel = (String) values[1];
		mask = (String) values[2];
		min = (String) values[3];
		max = (String) values[4];
		message = (String) values[5];
	}
	
	public static UIComponent findComponent(String id) {
		return findComponent(FacesContext.getCurrentInstance().getViewRoot(), id);
	}
	
	public static UIComponent findComponent(UIComponent base, String id) {
		if (id.equals(base.getId())) {
			return base;
		}		
		UIComponent kid = null;
		UIComponent result = null;
		Iterator kids = base.getFacetsAndChildren();
		while (kids.hasNext() && (result == null)) {
			kid = (UIComponent) kids.next();
			if (id.equals(kid.getId())) {
				result = kid;
				break;
			}
			result = findComponent(kid, id);
			if (result != null) {
				break;
			}
		}
		return result;
	}	
	
	public String getBundleMessage(FacesContext facesContext, String key){		
		String value = null;
		Locale locale = facesContext.getViewRoot().getLocale();
		ClassLoader loader = Thread.currentThread().getContextClassLoader();
		if (loader == null) {
			loader =   getClass().getClassLoader();
		}
		Application app = facesContext.getApplication();
		String appBundleName = app.getMessageBundle();
		if (appBundleName != null) {
			ResourceBundle bundle = ResourceBundle.getBundle(appBundleName, locale, loader);
			if (bundle != null) {
				try {
					value = bundle.getString(key);
				} catch (MissingResourceException ex) {
				}
			}
		}
		if(value == null){
			ResourceBundle bundle = ResourceBundle.getBundle(DEFAULT_BUNDLE_NAME, locale, loader);
			if (bundle != null) {
				try {
					value = bundle.getString(key);
				} catch (MissingResourceException ex) {
				}
			}
		}		
		return value==null?"":value;
	}
}